package com.heiye.user.biz.runner;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.heiye.framework.common.enums.DeletedEnum;
import com.heiye.framework.common.enums.StatusEnum;
import com.heiye.user.biz.constant.RedisKeyConstants;
import com.heiye.user.biz.domain.mapper.PermissionDOMapper;
import com.heiye.user.biz.domain.mapper.RoleDOMapper;
import com.heiye.user.biz.domain.mapper.RolePermissionDOMapper;
import com.heiye.user.biz.domain.dataobject.PermissionDO;
import com.heiye.user.biz.domain.dataobject.RoleDO;
import com.heiye.user.biz.domain.dataobject.RolePermissionDO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author: heiye
 * @date: 2024/09/22 下午7:54
 * @version: v1.0.0
 * @description: 角色-权限集合同步到redis中
 */
@Slf4j
@Component
public class PushRolePermissions2RedisRunner implements ApplicationRunner {
    @Resource
    private RoleDOMapper roleDOMapper;

    @Resource
    private RolePermissionDOMapper rolePermissionDOMapper;

    @Resource
    private PermissionDOMapper permissionDOMapper;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // 权限同步标记 Key
    private static final String PUSH_PERMISSION_FLAG = "push.permission.flag";

    @Override
    public void run(ApplicationArguments args) {
        log.info("==> 服务启动，开始同步角色权限数据到 Redis 中...");

        try {
            // 是否能够同步数据: 原子操作，只有在键 PUSH_PERMISSION_FLAG 不存在时，才会设置该键的值为 "1"，并设置过期时间为 1 天
            boolean canPushed = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(PUSH_PERMISSION_FLAG, "1", 1, TimeUnit.DAYS));

            // 说明已同步过
            if (!canPushed) {
                log.warn("==> 角色权限数据已经同步至 Redis 中，不再同步...");
                return;
            }

            // 查找出在启用的全部角色 id
            List<RoleDO> roleDOS = roleDOMapper.selectEnabledList();

            if (CollUtil.isNotEmpty(roleDOS)) {
                // 通过stream流将roles里的id提取出来
                List<Long> roleIds = roleDOS.stream().map(RoleDO::getId).toList();

                // 利用查询出来的角色 id ，来批量查询对应的权限 id
                List<RolePermissionDO> rolePermissionDOS = rolePermissionDOMapper.selectByRoleIds(roleIds);

                // 按角色 id 分组, 每个角色 id 对应多个权限 id
                Map<Long, List<Long>> roleIdPermissionIdsMap = rolePermissionDOS.stream().collect(
                        // 分类收集器
                        Collectors.groupingBy(
                                // 将rolePermissions的RoleId提取出来
                                RolePermissionDO::getRoleId,
                                // 转化为list
                                Collectors.mapping(
                                        // 将rolePermissions中与RoleId对应的PermissionId提取出来
                                        RolePermissionDO::getPermissionId,
                                        // 转化的类型
                                        Collectors.toList()
                                )
                        )
                );

                // 查询出在启用、未被删除以及类型为按钮的权限
                List<PermissionDO> permissionDOS = permissionDOMapper.selectAppEnabledList();

                // 转化为 权限id-权限 关系
                Map<Long, PermissionDO> permissionIdMap = permissionDOS.stream().collect(

                        // 转化map
                        Collectors.toMap(
                                // 取出 permission 里的 id 将其定为 key
                                PermissionDO::getId,
                                // 直接将整个 permission 定为 value
                                permissionDO -> permissionDO
                        )
                );

                // 组织 角色-权限 关系
                Map<String, List<String>> roleIdPermissionMap = Maps.newHashMap();

                // 循环所有角色
                roleDOS.forEach(roleDO -> {
                    // 获得当前角色id
                    Long roleId = roleDO.getId();

                    // 当前角色 roleKey
                    String roleKey = roleDO.getRoleKey();

                    // 查找当前角色 id 对应的权限 id 集合
                    List<Long> permissionIds = roleIdPermissionIdsMap.get(roleId);

                    if (CollUtil.isNotEmpty(permissionIds)) {

                        // 构建权限列表
                        // Lists.newArrayList() 由 Google Guava 库 提供，对java原生创建list方法进行了性能优化
                        List<String> permissionsList = Lists.newArrayList();

                        // 遍历角色拥有的权限
                        permissionIds.forEach(permissionId -> {

                            // 取得权限
                            PermissionDO permissionDO = permissionIdMap.get(permissionId);

                            // 添加到权限列表中
                            permissionsList.add(permissionDO.getPermissionKey());
                        });

                        // 添加到 角色id-权限 roleIdPermissionMap 中
                        roleIdPermissionMap.put(roleKey, permissionsList);
                    }
                });

                // 同步至 redis 中，方便后续网关查询鉴权使用
                roleIdPermissionMap.forEach((roleKey, permissionsList) -> {
                    // 构建对应key
                    String key = RedisKeyConstants.buildRolePermissionsKey(roleKey);
                    // 放入 redis 中
                    redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(permissionsList));
                });
            }

            log.info("==> 服务启动，成功同步角色权限数据到 Redis 中...");
        } catch (Exception e) {
            log.error("同步角色权限数据到 Redis 中失败: ", e);
        }
    }
}
